# @Time : 2025-01-11 14:46
# @Author : Fioman 
# @Phone : 13149920693
"""
plc通信的基类,基于pymodbus 的plc通信库
"""
from struct import pack, unpack

from pymodbus import ExceptionResponse
from pymodbus.client import ModbusTcpClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder

from common.exception_helper import error_handler_produce
from common.produce_logger import debug_produce
from personal_tool.data_result import DataResult


class PlcModbusBase:
    def __init__(self, ip: str, port: int):
        """
        初始化modbus客户端
        :param ip: ip地址
        :param port: 端口号
        """
        self.client = None
        self.ip = ip
        self.port = port

    @error_handler_produce
    def connect(self) -> bool:
        """
        连接PLC,返回是否连接成功
        """
        self.client = ModbusTcpClient(host=self.ip, port=self.port, timeout=2)
        connected = self.client.connect()
        return connected

    @error_handler_produce
    def write_word(self, address: int, value: int) -> DataResult:
        """
        写入单字整数(16位)
        :param address: 寄存器地址
        :param value: 写入值 (0-65535)
        """
        if not (0 <= value <= 65535):
            return DataResult(info="Invalid value for word,超出了范围")

        response = self.client.write_register(address=address, value=value)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"写入失败,type(response)={type(response)}")
        return DataResult(state=True)

    @error_handler_produce
    def write_double_word(self, address: int, value: int) -> DataResult:
        """
        写入双字整数(32位)
        :param address: 起始寄存器地址
        :param value: 写入值
        """
        if not (-2147483648 <= value <= 2147483647):
            return DataResult(state=False, data=None, info="Invalid value for double word,超出了范围")

        high_word = (value >> 16) & 0xFFFF
        low_word = value & 0xFFFF
        response = self.client.write_registers(address=address, values=[high_word, low_word])
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"写入失败,type(response)={type(response)}")
        return DataResult(state=True)

    def write_string(self, address: int, value: str, wordLength: int) -> DataResult:
        """
        写入字符串，固定长度写入
        :param address: 起始寄存器地址
        :param value: 写入的字符串
        :param wordLength: 要写入的字数（每个字可存储2个字符）
        :return: 是否成功
        """
        maxChars = wordLength * 2
        if len(value) > maxChars:
            debug_produce(f"String too long, truncating to {maxChars} characters")
            value = value[:maxChars]
        if len(value) < maxChars:
            value = value.ljust(maxChars, '\0')
        registers = []
        for i in range(0, maxChars, 2):
            high_byte = ord(value[i])
            low_byte = ord(value[i + 1])
            registers.append((high_byte << 8) | low_byte)

            # 分段写入（Modbus TCP建议每次最多写入123个寄存器）
        chunk_size = 123
        for i in range(0, len(registers), chunk_size):
            chunk = registers[i:i + chunk_size]
            response = self.client.write_registers(address=address + i, values=chunk)
            if response.isError():
                if isinstance(response, ExceptionResponse):
                    errorCode = response.exception_code
                    errorMessage = self._get_exception_message(errorCode)
                    return DataResult(info=errorMessage)
                return DataResult(info=f"写入失败,type(response)={type(response)}")
        return DataResult(state=True)

    @error_handler_produce
    def read_word(self, address: int) -> DataResult:
        """
        读取单字整数(16位)
        :param address: 寄存器地址
        :return: 读取的值
        """
        response = self.client.read_holding_registers(address=address, count=1)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"读取失败,type(response)={type(response)}")
        return DataResult(state=True, data=response.registers[0])

    @error_handler_produce
    def read_double_word(self, address: int) -> DataResult:
        """
        读取双字整数(32位)
        :param address: 起始寄存器地址
        :return: 读取的值
        """
        response = self.client.read_holding_registers(address=address, count=2)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"读取失败,type(response)={type(response)}")
        readVal = (response.registers[0] << 16) | response.registers[1]
        return DataResult(state=True, data=readVal)

    @error_handler_produce
    def read_string(self, address: int, wordLength: int) -> DataResult:
        """
        读取字符串，固定长度读取
        :param address: 起始寄存器地址
        :param wordLength: 要读取的字数（每个字可存储2个字符）
        :return: 读取的值
        """
        response = self.client.read_holding_registers(address=address, count=wordLength * 2)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"读取失败,type(response)={type(response)}")
        # 将寄存器值转换为字符串
        chars = []
        for value in response.registers:
            highChar = chr((value >> 8) & 0xFF)
            lowChar = chr(value & 0xFF)
            if highChar == '\0':  # 遇到空字符就停止
                break
            chars.append(highChar)
            if lowChar == '\0':  # 遇到空字符就停止
                break
            chars.append(lowChar)
        readVal = ''.join(chars)
        return DataResult(state=True, data=readVal)

    @error_handler_produce
    def write_bool(self, address: int, value: bool) -> DataResult:
        """
        写入布尔值
        :param address: 线圈地址
        :param value: 0 或 1
        """
        response = self.client.write_coil(address=address, value=value)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"写入失败,type(response)={type(response)}")
        return DataResult(state=True)

    @error_handler_produce
    def write_float(self, address: int, value: float, precision: int = 2) -> DataResult:
        """
        写入32位浮点数
        :param address: 起始寄存器地址
        :param value: 浮点数值
        :param precision: 保留小数位数，默认2位
        """
        # 四舍五入到指定小数位
        value = round(value, precision)

        # 将浮点数打包为IEEE 754格式的字节
        packed = pack('>f', value)

        # 转换为两个16位整数
        high_word = (packed[0] << 8) | packed[1]
        low_word = (packed[2] << 8) | packed[3]

        # 写入两个寄存器
        response = self.client.write_registers(address=address, values=[high_word, low_word])
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"写入失败,type(response)={type(response)}")
        return DataResult(state=True)

    @error_handler_produce
    def read_bool(self, address: int) -> DataResult:
        """
        读取布尔值,获取的结果也是bool值
        :param address: 线圈地址
        :return: 布尔值
        """
        response = self.client.read_coils(address=address, count=1)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"读取失败,type(response)={type(response)}")
        # 读取的值为0或者1转换true或者false
        return DataResult(state=True, data=response.bits[0])

    @error_handler_produce
    def read_float(self, address: int, precision: int = 2) -> DataResult:
        """
        读取32位浮点数
        :param address: 起始寄存器地址
        :param precision: 保留小数位数，默认2位
        :return: 浮点数值
        """
        response = self.client.read_holding_registers(address, count=2)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"读取失败,type(response)={type(response)}")

        # 组合两个16位整数
        high_word = response.registers[0]
        low_word = response.registers[1]

        # 转换为字节
        bytes_val = bytes([
            (high_word >> 8) & 0xFF,
            high_word & 0xFF,
            (low_word >> 8) & 0xFF,
            low_word & 0xFF
        ])

        # 解包为浮点数
        float_val = unpack('>f', bytes_val)[0]

        # 四舍五入到指定小数位
        float_val = round(float_val, precision)

        return DataResult(state=True, data=float_val)

    @error_handler_produce
    def write_float_use_builder(self, address: int, value: float) -> DataResult:
        """
        写入双字浮点数(32位)
        :param address: 起始寄存器地址
        :param value: 要写入的浮点数
        :return: 是否成功
        """
        builder = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
        builder.add_32bit_float(value)
        registers = builder.to_registers()

        response = self.client.write_registers(address=address, values=registers)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"写入失败,type(response)={type(response)}")

        return DataResult(state=True)

    @error_handler_produce
    def read_float_use_builder(self, address: int) -> DataResult:
        """
        读取双字浮点数(32位)
        :param address: 起始寄存器地址
        :return: 读取的浮点值
        """
        response = self.client.read_holding_registers(address=address, count=2)
        if response.isError():
            if isinstance(response, ExceptionResponse):
                errorCode = response.exception_code
                errorMessage = self._get_exception_message(errorCode)
                return DataResult(info=errorMessage)
            return DataResult(info=f"读取失败,type(response)={type(response)}")

        decoder = BinaryPayloadDecoder.fromRegisters(response.registers,
                                                     byteorder=Endian.BIG, wordorder=Endian.LITTLE)
        float_value = decoder.decode_32bit_float()
        return DataResult(state=True, data=float_value)

    def __del__(self):
        """
        析构函数中关闭连接
        """
        try:
            if hasattr(self, 'client') and self.client is not None:
                self.client.close()
        except:
            pass

    def _get_exception_message(self, code: int) -> str:
        """
        获取Modbus异常代码对应的错误描述
        """
        exception_messages = {
            1: "Illegal Function (功能码错误)",
            2: "Illegal Data Address (数据地址错误)",
            3: "Illegal Data Value (数据值错误)",
            4: "Slave Device Failure (从站设备故障)",
            5: "Acknowledge (确认)",
            6: "Slave Device Busy (从站设备忙)",
            7: "Negative Acknowledge (否认)",
            8: "Memory Parity Error (内存奇偶校验错误)",
            10: "Gateway Path Unavailable (网关路径不可用)",
            11: "Gateway Target Device Failed to Respond (网关目标设备无响应)"
        }
        return exception_messages.get(code, f"Unknown Error Code: {code}")
